use godot::classes::{mesh::PrimitiveType, ArrayMesh};
use godot::prelude::*;

/// This mesh builder is for building meshes using Godot's `PRIMITIVE_TRIANGLES`
/// primitive mesh type.  This type of mesh groups triplets of vertexes as
/// singular triangles.  Each triangle vertex has an associated normal vector
/// and UV coordinate.
///
/// See also [Godot docs](https://docs.godotengine.org/en/stable/classes/class_mesh.html#enum-mesh-primitivetype).
pub struct MeshBuilder {
    /// Vertexes passed to the mesh.
    verts: Vec<Vector3>,

    /// Each triplet here is usually all the same.
    vert_normals: Vec<Vector3>,

    /// UVs passed to the mesh.
    vert_uvs: Vec<Vector2>,
}

impl MeshBuilder {
    pub fn new() -> Self {
        Self {
            verts: Vec::new(),
            vert_normals: Vec::new(),
            vert_uvs: Vec::new(),
        }
    }

    /// Returns the number of verts.
    pub fn num_verts(&self) -> usize {
        self.verts.len()
    }

    /// Returns the number of triangles, computed from the number of verts.
    pub fn num_triangles(&self) -> usize {
        self.num_verts() / 3
    }

    /// Sanity checks the properties.
    fn sanity_check(&self) {
        assert_eq!(
            self.verts.len(),
            self.vert_normals.len(),
            "verts and normals mismatch"
        );
        assert_eq!(
            self.verts.len(),
            self.vert_uvs.len(),
            "verts and uvs mismatch"
        );
    }

    /// Adds a triangle, using the same normal for all 3 verts.
    pub fn add_triangle_norm(
        &mut self,
        ap: Vector3,
        auv: Vector2,
        bp: Vector3,
        buv: Vector2,
        cp: Vector3,
        cuv: Vector2,
        norm: Vector3,
    ) {
        // Append everything.
        self.verts.reserve(3);
        self.vert_normals.reserve(3);
        self.vert_uvs.reserve(3);
        self.verts.push(ap);
        self.verts.push(bp);
        self.verts.push(cp);
        self.vert_normals.push(norm);
        self.vert_normals.push(norm);
        self.vert_normals.push(norm);
        self.vert_uvs.push(auv);
        self.vert_uvs.push(buv);
        self.vert_uvs.push(cuv);

        // Just in case.
        #[cfg(debug_assertions)]
        self.sanity_check();
    }

    /// Adds a triangle, inferring the normals of each vertex based on the vertex positions.
    ///
    /// See [Khronos docs](https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal).
    pub fn add_triangle_flat(
        &mut self,
        ap: Vector3,
        auv: Vector2,
        bp: Vector3,
        buv: Vector2,
        cp: Vector3,
        cuv: Vector2,
    ) {
        // Quick normal calculation.
        let nu = bp - ap;
        let nv = cp - ap;
        let nx = nu.x * nv.z - nu.z * nv.y;
        let ny = nu.z * nv.x - nu.x * nv.z;
        let nz = nu.x * nv.y - nu.y * nv.x;
        let norm = Vector3::new(nx, ny, nz);

        self.add_triangle_norm(ap, auv, bp, buv, cp, cuv, norm);
    }

    /// Completes the mesh, emitting a Godot `ArrayMesh`.
    pub fn build_godot(&self) -> Gd<ArrayMesh> {
        self.sanity_check();

        // Copy the raw data into the Godot types.
        let verts = Variant::from(PackedVector3Array::from(self.verts.as_slice()));
        let normals = Variant::from(PackedVector3Array::from(self.vert_normals.as_slice()));
        let uvs = Variant::from(PackedVector2Array::from(self.vert_uvs.as_slice()));

        // Construct the specially-structured mesh array.
        let mut mesh_arr = Array::new();
        mesh_arr.push(verts);
        mesh_arr.push(normals);
        mesh_arr.push(Variant::nil()); // tangents
        mesh_arr.push(Variant::nil()); // vertex colors
        mesh_arr.push(uvs);

        // Fill the rest up to ARRAY_MAX with nils.
        while mesh_arr.len() < 13 {
            mesh_arr.push(Variant::nil());
        }

        // Construct the mesh and add the surface to it.
        let mut mesh = ArrayMesh::new_gd();
        mesh.add_surface_from_arrays(PrimitiveType::TRIANGLES, &mesh_arr);
        mesh
    }
}
